package server;

import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import Log.*;

import interfaces.Master;
import interfaces.Peer;

public class PrimePeer implements Peer {
    boolean electionHasStarted = false;
    boolean IAmMaster = false; 
    
	private Registry registry;
	private String myName;
    
	public PrimePeer(Registry registry, String name) {
		this.registry = registry;
		this.myName = name;
	}
	
	public boolean IAmMaster() {
		return IAmMaster;
	}

	// return false : election has been started by others.
	//        true : election succeeds.
	public boolean election() {
		if (electionHasStarted) {
			return false;
		}
		
		// check if the master still exists.
		Master master;
		try {
			master = (Master) registry.lookup("master");
			master.ping();
			// The master exists. You just didn't get the message.
			return false;
		} catch (RemoteException e1) {
			Log.log(MessageType.Election, "Someone ask me to start election.");
		} catch (NotBoundException e2) {
			Log.log(MessageType.Election, "Someone ask me to start election.");
		}
		
		electionHasStarted = true;

		String[] allEntries = null;
		try {
			allEntries = registry.list();
		} catch (Exception e) {
			Log.log(MessageType.Error, "Access registry failed.");
			throw new RuntimeException(e.getMessage());
		}
		
		// Prepare an ordered entry list that only has peers. 
		ArrayList<String> entries = new ArrayList<String>();
		for (String entry : allEntries) {
			entries.add(entry);
		}
		entries.remove("master");
		Collections.sort(entries);

		int i = entries.indexOf(myName);
		
		List<String> largerEntries = null;
		
		largerEntries = entries.subList(i + 1, entries.size());
		
		boolean result = false;
		
		// No greater peers.
		if (largerEntries.size() == 0) {
			becomeMaster(registry, entries);
		} else { // There are greater peers.
			int nDeadPeers = 0;
			for (String entry : largerEntries) {
				try {
					Peer peer = (Peer) registry.lookup(entry);
					result = peer.election();
					break;
				} catch (RemoteException e) {
					Log.log(MessageType.Error, entry + "is dead.");
					nDeadPeers++;
					continue;
				} catch (Exception e) {
					Log.log(MessageType.Error, "Access registry failed.");
					throw new RuntimeException(e.getMessage());
				}
			}
			// All greater peers are dead.
			if (nDeadPeers == largerEntries.size()) {
				becomeMaster(registry, entries);
			}
		}
		
		if (!result) {
			// If election has been started by others.
			// This peer periodically check until the election is over.
			while (true) {
				try {
					Log.log(MessageType.Election, "Election has been started by others. Wait 1 sec.");
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}					
				if (!electionHasStarted) // election is over.
					break;
			}
		}
		
		return result;
	}

	private void becomeMaster(Registry registry, ArrayList<String> entries) {
		PrimeMaster master = new PrimeMaster();
		
		Master masterStub = null;
		try {
			masterStub = (Master) UnicastRemoteObject.exportObject(master, 0);
		} catch (RemoteException e) {
			Log.log(MessageType.Error, "Cannot create master registry.");
			throw new RuntimeException(e.getMessage());
		}

		while (true) {
			// Periodically check until the stale master entry has been removed.
			try {
				Thread.sleep(1000);
				registry.bind("master", masterStub);				
				break;
			} catch (AlreadyBoundException e) {
				Log.log(MessageType.Election,
						"The stale master registry is still there. Wait 1 s.");
			} catch (Exception e) {
				Log.log(MessageType.Error, "Cannot bind the master entry.");
				throw new RuntimeException(e.getMessage());
			}
		}

		IAmMaster = true;
		// notify every one that I am the master
		for (String entry : entries) {
			Peer peer;
			try {
				peer = (Peer) registry.lookup(entry);
			} catch (NotBoundException e1) {
				Log.log(MessageType.Error, entry + "is dead.");
				continue;
			} catch (Exception e) {
				Log.log(MessageType.Error, "Access registry failed.");
				throw new RuntimeException(e.getMessage());
			}
			
			try {
				peer.electionSucceeds();
			} catch (RemoteException e) {
				Log.log(MessageType.Error, entry + "is dead.");
			}
		}		
	}
	
	public void electionSucceeds() {
		electionHasStarted	= false;
	}
}
